In the DotNet world of web application development, there are two main approaches, ASP.Net and MVC. ASP.Net has been around since the very first release of DotNet. This is the approach that most people familiar with DotNet have been using. MVC is the new kid on the block and was only recently released. You may wonder why the need for a new method. Put simply, it is here to provide options.
MVC stands for Model View Controller and is designed to make it easier to structure web applications following the MVC design pattern. We want web applications to follow the MVC design pattern (or a similar pattern) to simplify testing, make it easier to deploy to alternate web platforms, and provide better separation of responsibilities. Code embedded in the UI is not so easy to test, but we can easily automate testing code in a class library. Seperation of responsibilities helps us to move code that does not have to be in the UI out to a separate class library.
MVC also turns ASP.Net on its head eliminating ViewState, removing the Event Model, and adding routing all for the promise of greater developer control and of course making it easier to follow the MVC design pattern.
But what if you want to reap the benefits of MVC without leaving the comfort of traditional ASP.Net? There is still hope…
MVC with Traditional ASP.Net
ASP.Net has separation of responsibilities allowing you to put the markup in the ASPX file and the code in the Code – Behind file. This makes web development more straight forward vs. Classic ASP where both were meshed together. This separation allows you to focus on formatting and content in the markup file and logic and data binding in the code behind. This allows you to potentially have different people working on different aspects of the system or simply allow the individuals working on the different parts of the application without having to worry about the other.
Following an MVC design pattern whether you use MVC or traditional ASP.Net takes things a step further. The goal is to separate concerns even further. In an MVC implementation, any logic that does not have to be in the UI is moved out of the UI and into the Presenter. The Presenter is a separate class potentially in a separate assembly that is responsible for all of the non UI specific logic. This can include data binding, validation, workflow, etc. Logic such as responding to events directly raised from the UI controls would still be handled in the Code Behind, but even then most of the processing would be handed over to the Presenter.
A Comparison of Patterns
We have played fast and loose with pattern names, and I have hinted that there is more than one potential pattern that can serve our goals. This leads to confusion that we can hopefully clear up here.
There are three related patterns that are often used interchangeably with the differences debated by die hard purists and ignored and meaningless to casual observers. We are talking about Model View Controller, Model View Presenter, and Passive View. All three have a Model and a View. In each case there is general agreement about the role and nature of the Model. The Model is your data source. It should not have any business logic. It should house only data logic such as mapping, validations, and relationships. The View is often the most contentious object. The main difference between these three patterns is how much responsibility to leave with the View. Model View Controller leaves more responsibility than any of the other patterns. Here, the View can update the Model directly.
Model View Presenter limits some of what the View is expected to do, or allowed to do. The View knows nothing about the Model, but it does interact with the Controller.
Passive View requires that there the View have virtually no responsibility. It will literally pass all responsibility to the Controller or Presenter. It does not interact with the Model at all. In fact, under some implementations, the View may be completely unaware that there even is a presenter.
As you can see, the differences are subtle. For the purposes of this article, we will side step these differences. What we are most interested in is how to structure a WebPage in an ASP.Net web application so that it is ready to behave like a View.
Benefits of Views
Code that is not part of the User Interface (UI) is easier to test and can be reused if we switch the UI platform. In other words, any code in the UI has to be manually tested and all of the code embedded in the UI must be recreated if we switch UI platforms such as switching from DNN to SharePoint or from a Web Application to Mobile Application.
Having all of the code or at least all of the logic from the UI being testable through automated regression tests improves stability immensely and allows you to refactor your code with much more confidence. Being able to target multiple UIs with minimal rewrite is one of the Holy Grails of software development.
A Web Page is a First Class Object
It is important to remember that the class in the code-behind is a first class object it its own right. It is as valid as any Built In data type in the framework. This means that we can pass the WebPage to other objects to do the heavy lifting to implement our business logic. More precisely, we can pass to a Presenter a carefully-structured interface that our WebPage implements, so that the Presenter can handle the business logic.
The interface will limit the scope of what gets passed to the Presenter. It will also absolve the Presenter from any need to know about the UI platform being used. This helps make the Presenter more versatile. If, for example, the Presenter is written with the assumption that the WebPage is part of a Share Point solution, then the Presenter may be useless if our app is moved to a custom web application, a mobile application, or a WinForm application.
I will refer to a WebPage setup to serve the role of a View as a WebPage View.
Towards Some Best Practices
Encapsulate control access in properties. For example, instead of having lines like this in your code…
1 |
if (txtFirstName.Text == string.Empty) |
…You should define a property FirstName and have code like this:
1 |
if (this.FirstName == string.Empty ) |
The same thing applies to Query Strings and View State
- Move all conditional logic out of the Code-Behind. This may not seem achievable, but look closely at any method or property with a cyclomatic complexity greater than 2. I am very curious to hear about why you would need a higher complexity. I believe that in nearly all cases, the logic is better served in a Presenter.
- Restrict the interaction with the Controller or Presenter to interfaces. There are several variations in the basic pattern that may be used. Model View Presenter, Passive View, Supervising Controller, etc. There is also some terminology debate over whether it is a Controller or a Presenter. For purposes of our best practices, let’s side-step these debates.
We want the Controller or Presenter object to interact with the View solely through interfaces regardless of how much work you allow the View to do,. You should have an IView interface and an IPresenter interface. Everything that the View knows about the Presenter should be defined in the IPresenter interface and everything that the Presenter knows about the View should be defined in the IView interface
- The IView interface should give no details about the UI platform. If you would need to change anything in the IView interface to switch from a DNN portal to SharePoint, you need to restructure your interface.
Encapsulation
I see these patterns all the time embedded directly in code.
1 2 3 4 5 |
if (txtNextDueDate.Text != string.Empty) { businessObject.NextDueDate = DateTime.Parse(txtNextDueDate.Text); } |
Or
1 |
businessEntity.PrimaryKey = (int)ViewState["primaryKey"]; |
Or
1 2 |
int UserId = Int32.Parse (Request.QueryString ["userId"]. ToString ()); |
Each of these examples are have various problems . The code is awkward to read. The type-conversion logic gets in the way of the true flow of the code. There are validations that are embedded in the code. If these validations are repeated everywhere that the value is referenced, we have duplicate code. If these validations are not repeated, we will have an unstable application or logic problems.
Such code snippets make it harder to follow the process logic, and also make it harder to separate the business logic from the UI.
There is also a problem with embedding access to the ViewState and the QueryString. In both cases, we have an embedded magic string that must be used consistently or there will be problems. Naturally, the magic string should be replaced with a constant, but that does not fully resolve the problem.
The biggest problem is that UI-specific components are embedded in logic that will need to be extracted out of the UI. We cannot extract code from the UI that refers to View State, Query String, or UI controls, but we can easily extract code that refers to simple properties.
We can resolve all of these concerns by encapsulating the values with properties. The code above can reasonably be rewritten to:
1 2 3 4 5 |
businessObject.NextDueDate = this.NextDueDate; businessEntity.PrimaryKey = this.PrimaryKey; int UserId = this.UserId; |
The properties can have implementations similar to the original code that we saw earlier except with more appropriate error handling and validation logic.
The implementation of the properties provides a centralized place to store the validation and type conversion logic. We can safely use the properties throughout our code without having to worry about the logic. If there is a problem identified with this logic, we only have to make a change to one place.
These properties also encapsulate how the UI stores and retrieves the values, meaning that we can safely use the properties without worrying about the underlying UI platform.
Move Conditional Logic out of the Code-Behind
As we previously stated, it is not possible to Move Conditional Logic out of the Code-Behind until after we have adopted the first best practice of encapsulation. Even after this vital step, there is still much work to be done. Once you have built out the necessary properties, the repetitive patterns will be more obvious.
Conditional logic that commonly finds its way into the UI can be categorized three ways. Conditional logic that is purely business-oriented, conditional logic used to implement the properties we just discussed, and conditional logic that is a mix between business logic and UI logic.
The pure business logic is perhaps the easiest to move. This may generally involve a series of “extract method” refactors to isolate the conditional logic and move method refactors in order to move the extracted code to the presenter. For the most part, this will result in the original code being moved from the Code-Behind to the presenter. In most cases, the only changes will be to change references to “this” to references to “view” or whatever variable the Presenter uses to refer back to the View.
Most of the properties that we created in the first best practice will include very similar validation logic and type conversion logic. In many cases the only difference between properties will be the name of the control involved. Once you see this pattern emerge, you can begin extracting common redundant functionality to a base class.
Code like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public int? PrimaryKey { get { if (ViewState["primaryKey"] != null) { return (int)ViewState["primaryKey"]; } else { return null; } } set { ViewState["primaryKey"] = value; } } |
Becomes
1 2 3 4 5 6 7 8 9 10 11 12 |
public int? PrimaryKeyNew { get { return SafeExtractInt("primaryKey"); } set { ViewState["primaryKey"] = value; } } |
Code like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
public DateTime? NextDueDate { get { DateTime? returnValue = null; if (txtNextDueDate.Text != string.Empty) { DateTime parseResult; if (DateTime.TryParse (txtNextDueDate.Text, out parseResult)) { returnValue = parseResult; } } return returnValue; } set { if (value.HasValue) { txtNextDueDate.Text = value.Value.ToShortDateString(); } } } |
Becomes
1 2 3 4 5 6 7 8 9 10 11 |
public DateTime? NextDueDate { get { return SafeConvertTextToDateTime(txtNextDueDate); } set { SafeSetDateValue(txtNextDueDate, value); } } |
You may need multiple overloads for the various input types, but you can get to the point where each of your properties can be reduced to a single line of code for the get and the set. Across even a modest sized web application, these helper methods will get tremendous reuse.
Sometimes business logic is mixed with UI code and may seem difficult to extract. This code can also be removed from the Code-Behind. You may often see code like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public void Validate() { DateTime targetDueDate = DateTime.Today.AddDays(30); if (NextDueDate < targetDueDate) { // take one course of action; } if (NextDueDate == targetDueDate) { // take some other action; } if (NextDueDate > targetDueDate) { // take some other action; } } |
Sometimes there is a strong temptation to keep this in the UI, especially if the actions to be taken involve the UI such as displaying a message to the user or altering another control’s state. Even then we are not stuck with conditional logic in the UI. Instead we can move this code to the presenter:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public void Validate() { DateTime targetDueDate = DateTime.Today.AddDays(30); if (View.NextDueDate < targetDueDate) { View.ProcessSoonDueDate(); } if (View.NextDueDate == targetDueDate) { View.ProcessStandardDueDate(); } if (View.NextDueDate > targetDueDate) { View.ProcessDistantDueDate(); } } |
We are now requiring that the View implement three new methods. These methods should still be fairly straightforward and should not have any conditional logic, simply processing logic.
The UI does not have to get bogged down with how the due date is evaluated and the Presenter does not need to worry about what happens with each type of due date. Each of the new methods required for the View can only have to worry about handling the individual simple cases.
Restrict Interaction to Interfaces
Separating the logic between the Presenter and the View has several advantages. It makes the UI easier to test, moves the business logic to a format that they can easily be reused, and it separates responsibilities so that the effort can more easily be shared. Part of the magic of this approach relies on the View and the Presenter participating in a Bridge Pattern where the individual implementations can vary without affecting each other. This means that the View and the Presenter must both implement interfaces and interact with each other strictly through that interface.
The IView interface is the only thing that the Presenter should know about the View. The IPresenter interface is the only thing that the View should know about the Presenter. These interfaces should deliberately convey as little information as absolutely required. Ideally the Presenter will get a reference to the View when it is created through an overloaded constructor. If the View needs a reference to the Presenter, it can come from either a factory or a view initializer, usually in the form of a containing page.
In some implementations, the importance of the IPresenter interface is diminished because the View knows nothing about the presenter. Remember that the distinction between Model View Presenter, Passive View, and Supervising Controller boils down to how much control and responsibility you leave with the view.
Regardless of the actual pattern followed, the ‘separation by interfaces’ is important.
So there are a couple of things to keep in mind when creating these interfaces.
- Keep the interfaces as simple as possible. Use the simplest datatypes possible for defining properties and parameters.
- Limit the amount of information conveyed through the interface definition. The View should not be able to tell where data comes from or where the data is stored by examining the IPresenter Interface. The same is true for the IView interface.
- Follow reasonable naming conventions in the interface definition. If practical allow the interfaces to inherit from simpler interfaces. For example, all presenter interfaces may derive from IBasePresenter and all view interfaces may derive from IBaseView. The concrete objects will follow a similar inheritance hierarchy; let this also simplify the interface definitions.
Well defined and structured interfaces can mean the difference between a beautiful implementation and a klutzy awkward mess. Do not underestimate the importance of this step.
Make the IView Interface Platform Agnostic
This best practice draws from the previous one, and there is a great deal of overlap, but this point is so important and so frequently misapplied that it deserves separate attention.
The IView interface cannot reveal any details about the underlying UI platform. Even if there is no chance of ever changing an implementation detail such as prompting for the FirstName in a text box, the TextBox cannot be the data type for a property. We want to use the simplest data types possible, and a string is dramatically simpler than a TextBox.
Even if you know that you will never port the application to another web platform, do not reveal anything about the web platform being used through the interface. Even if you are certain that you will never switch to a Mobile App, and you know for sure that you will never make it a SharePoint portal, you could be wrong. You may also decide to switch and start pulling FirstName from a drop down or start using a Third Party Control or add some Ajax extension, or spruce up the application with some SilverLight. As long as the interfaces are expecting a string and not a TextBox, you not have to touch the presenter and all of its logic; you will only need to change the implementation of a fairly simple property.
How Does This Change What We Do
Following these recommendations will improve the quality of your code, whether you follow all of these best practices or just some of them. Of course, I believe the more you can follow the more benefit you will find.
Load comments